from rlinterface import InputHandler
from erlglobals import *
from rlkbinput import *
from erlmenu import *
from erlcomponents import *

class DirectionPrompt (InputHandler):
	def __init__(self, interface, do, cancelled = None):
		InputHandler.__init__(self, interface)
		self.interface = interface
		self.do = do
		self.cancelled = cancelled
	def handle(self, event):
		key = KeyboardInterpreter( event )
		vector = max( [ key.asViVector(), key.asNumpadVector() ] )
		if vector:
			self.quit()
			self.do( vector )
		elif key.isAscii(" "):
			if self.cancelled:
				self.interface.messages.add( self.cancelled )
			self.quit()

class ContinueOnSpace (InputHandler):
	def __init__(self, interface, do):
		InputHandler.__init__(self, interface)
		self.interface = interface
		self.do = do
	def handle(self, event):
		key = KeyboardInterpreter( event )
		if key.isAscii(" "):
			self.quit()
			self.do()


class MainInputHandler (InputHandler):
	def __init__(self, game, pc, timekeeper ):
		InputHandler.__init__(self,game.interface)
		self.pc = pc
		self.timekeeper = timekeeper
		self.game = game
	def handle(self, event):
		self.interface.elements["text"].clear()
		key = KeyboardInterpreter( event )
		movement = None
		if not movement: movement = key.asViVector()
		if not movement: movement = key.asUppercaseViVector()
		if not movement: movement = key.asNumpadVector()

		isFrozen = self.game.at.frozenCountdown > 0

		if key.isAscii( "Q" ):
			self.quit()
			raise PlayerQuit()
		elif key.isAscii( "." ) or key.isNumpadCenter(): 
			self.actionWait()
		elif not isFrozen:
			if movement:
				if key.isShifted():
					self.actionTurn( movement )
				else:
					self.actionTryMove( movement )
			elif key.isAscii( "f" ) and self.pc.facing:
				self.actionFire()
			elif key.isAscii( "w" ):
				self.actionWield()
			elif cheatReveal and key.isAscii( "z" ):
				self.actionDebugFovCheat()
			elif key.isAscii( "d" ):
				self.actionDrop()
			elif key.isAscii( "x" ):
				self.actionDisplace()
			elif key.isAscii( "o" ):
				self.actionToggleDoor()
			elif key.isAscii( "," ):
				self.actionTryPickup()
			elif key.isAscii( "i" ):
				self.uiShowInventory()
			elif key.isAscii( "s" ):
				self.actionSetDial()
			elif key.isAscii( "T" ):
				self.actionBuild()
			elif key.isAscii( "t" ):
				self.actionThrow()
			elif key.isAscii( "r" ):
				self.actionLoad()
			elif key.isAscii( "D" ):
				self.actionDesign()
			elif cheatWinButton and key.isAscii( "W" ): 
				raise PlayerWon()
			elif key.isAscii( "c" ): 
				pass # self.actionCable() : ask for source: cart, item? [direction, which?] socket? ask for dest: same. link. cables are constant-length and virtual.
			elif key.isAscii( ">" ):
				self.actionDown()
			elif key.isAscii( "<" ):
				self.actionUp()
	def uiSave(self):
		self.game.save()
		# self.quit()
	def actionWait(self):
		self.pc.frozenCountdown = max( self.pc.frozenCountdown - 1 , 0 )
		self.pc.generateFov()
		self.timekeeper.tick()
	def actionThrow(self):
		if not self.pc.readiedItem:
			self.interface.messages.add( "You don't have any devices readied to throw." )
			return
		throwLength = 8
		item = self.pc.readiedItem
		self.pc.readyItem( None, silent = True )
		taken = item.take(1)
		def throwDirection( dir ):
			if not dir: return
			self.interface.messages.add( "You throw the %s." % taken.commonName() )
			projector = EffectProjectorBouncerBall( self.interface, self.pc.level, None, (self.pc.x,self.pc.y), dir, throwLength )
			destx, desty = projector.area[-1]
			projector.displayGlyph( taken.itemType.glyph )
			self.pc.level.putItem( taken, destx, desty )
			self.interface.refresh()
			self.actionWait()
		throwDirection( self.pc.facing )
#		self.interface.messages.add( "Where do you wish to throw your %s?" % taken.commonName() )
#		DirectionPrompt( self.interface, do = throwDirection, cancelled = "Cancelled.")
	def actionSetDial(self):
		if not self.pc.readiedItem:
			self.interface.messages.add( "You don't have any devices readied for use." )
			return
		getCWDials = getattr( self.pc.readiedItem, "componentsWithDials", None )
		if not callable( getCWDials ) or not getCWDials():
			self.interface.messages.add( "There are no dials to set on the %s." % self.pc.readiedItem.commonName() )
			return
		def selectComponent( component ):
			def selectDial( dial ):
				def selectValue( value ):
					component.property[dial][0].value = value
					self.actionWait()
				if not dial: return
				vals = component.property[dial][0].getValues()
				self.optionChoice(
					[ str(v) for v in vals ],
					vals,
					selectValue
				)
			if not component: return
			if len( component.dials() ) == 1:
				selectDial( component.dials()[0] )
			else:
				self.optionChoice(
					component.dials() + [ "<DARKBRASS>none</DARKBRASS>" ],
					component.dials() + [ None ],
					selectDial
				)
		components = getCWDials()
		if len( components ) == 1:
			selectComponent( components[0] )
		else:
			self.optionChoice(
				[ component.description() for component in components ] + [ "<DARKBRASS>none</DARKBRASS>" ],
				components + [ None ],
				selectComponent )
	def actionDown(self):
		tile = self.pc.level.tiles[self.pc.x][self.pc.y]
		if tile.type != self.game.gtt.stairsDown:
			self.game.interface.messages.add( "You can't see any stairs going down here." )
		else:
			down = self.game.getLevelBelow( self.pc.level )
			x, y = down.getRandomTileOfType( self.game.gtt.stairsUp )
			down.relocateMobileAt( x, y, self.game.gtt.floor )
			self.pc.move( down, x, y )
			self.actionWait()
	def actionUp(self):
		tile = self.pc.level.tiles[self.pc.x][self.pc.y]
		if tile.type != self.game.gtt.stairsUp:
			self.game.interface.messages.add( "You can't see any stairs going up here." )
		else:
			up = self.game.getLevelAbove( self.pc.level )
			if not up:
				self.game.interface.messages.add( "There's no sense in going back to the surface yet." )
			else:
				x, y = up.getRandomTileOfType( self.game.gtt.stairsDown )
				up.relocateMobileAt( x, y, self.game.gtt.floor )
				self.pc.move( up, x, y )
				self.actionWait()
	def actionFire(self):
		if not self.pc.readiedItem:
			self.interface.messages.add( "You don't have any devices readied for use." )
			return
		getTriggers = getattr( self.pc.readiedItem, "triggers", None )
		if not getTriggers:
			self.interface.messages.add( "The %s isn't meant to be fired." % self.pc.readiedItem.itemType.name.singular )
			return
		triggers = getTriggers()
		if not triggers:
			self.interface.messages.add( "The %s doesn't have any triggers you could pull." % self.pc.readiedItem.itemType.name.singular )
			return
		if len( triggers ) == 1:
			chosenTrigger = triggers[0]
			self.pc.readiedItem.pullTrigger( chosenTrigger)
			self.pc.generateFov()
			self.timekeeper.tick()
		else:
			def doChooseTrigger( trigger ):
				if not trigger: return
				self.pc.readiedItem.pullTrigger( trigger )
				self.pc.generateFov()
				self.timekeeper.tick()
			self.optionChoice(
				[ trigger.description() for trigger in triggers ] + [ "<DARKBRASS>none</DARKBRASS>" ],
				triggers + [ None ],
				doChooseTrigger )
	def actionLoadDevice(self, device):
		if not hasattr( self.pc.readiedItem, "containers" ) or not self.pc.readiedItem.containers():
			self.interface.messages.add( "There aren't any chambers to load things into on the %s." % self.pc.readiedItem.commonName() )
			return
		def chooseContainer( container ):
			def chooseItem( item ):
				if not item: return
				try:
					container.loadItems( item )
				except CannotLoadItem:
					self.interface.messages.add( "That won't fit in there." )
				self.actionWait()
			InventoryMenu( self.pc.inventory, screenWidth, screenHeight, onAnswer=chooseItem, query="Load what into the %s?" % container.description(), interface=self.interface, name = "load-item")
		containers = device.containers()
		if len( containers ) == 1:
			chooseContainer( containers[0] )
		else:
			self.optionChoice(
				[ c.description() for c in containers ] + [ "<DARKBRASS>none</DARKBRASS>" ],
				containers + [ None ],
				chooseContainer
			)
	def actionLoad(self):
		if not self.pc.readiedItem:
			self.interface.messages.add( "You're not holding anything you could load." )
		else:
			self.actionLoadDevice( self.pc.readiedItem )
	def optionChoice(self, desc, value, onAnswer, query = None):
		OptionChoice( query=query, width=60, descriptions=desc, values=value, onAnswer=onAnswer, interface = self.interface, name="mainMenu-%s" % randomString() )
	def uiExamine(self, item ):
		if item == None: return
		text = []
		text.append( "<GREEN>" )
		text.append( item.description() )
		text.append( "</GREEN>" )
		usable = getattr( item.itemType, "usable", None )
		if usable:
			def doUseItem( meth ):
				if callable( meth ): meth( self.pc, item )
			text.append( "<N>Use the %s now?" % item.commonName() )
			text = "".join( text )
			descs = [ "Yes", "No" ]
			values = [ usable , None ]
			OptionChoice( query=text, width=60, descriptions=descs, values=values, onAnswer=doUseItem, interface = self.interface, name = "examine-item" ) 
		else:
			text = "".join( text )
			TextDisplay( text, 60, interface = self.interface, name = "examine-item" ) 
	def uiShowInventory(self):
		InventoryMenu( self.pc.inventory, screenWidth, screenHeight, onAnswer=self.uiExamine, query=None, interface=self.interface, name = "examine-item")
	def actionDropItem(self, item):
		if item == None: return
		def actionDropItemAmount(amount):
			if amount <= 0: return
			takenItem = item.take( amount )
			self.interface.messages.add( "You drop %s." % takenItem.description() )
			self.pc.level.putItem( takenItem, self.pc.x, self.pc.y )
			takenItem.facing = self.pc.facing
			self.pc.generateFov()
			self.timekeeper.tick()
		

			if takenItem and self.pc.readiedItem and takenItem.sameItem( self.pc.readiedItem ) and not self.pc.inventory.findType( takenItem.itemType ):
				self.pc.readyItem( None )
		if item.amount > 1:
			AmountChoice( interface=self.interface, query=None, width=60, selection=item.amount, minimum=0, maximum=item.amount, descriptor=ItemCounter(item.itemType), name="amount-drop", onAnswer=actionDropItemAmount, priority=2)
		else:
			item = item.take( 1 )
			self.interface.messages.add( "You drop %s." % item.description() )
			self.pc.level.putItem( item, self.pc.x, self.pc.y )
			item.facing = self.pc.facing
			self.pc.generateFov()
			self.timekeeper.tick()
			if item and self.pc.readiedItem and item.sameItem( self.pc.readiedItem ) and not self.pc.inventory.findType( item.itemType ):
				self.pc.readyItem( None )
	def actionDrop(self):
		InventoryMenu( self.pc.inventory, screenWidth, screenHeight, onAnswer=self.actionDropItem, query="Drop what?", interface=self.interface, name = "drop-item")
	def actionWieldItem(self, item):
		if not item: return
		if self.pc.readyItem( item ):
			self.pc.generateFov()
			self.timekeeper.tick()
	def actionWield(self):
		InventoryMenu( self.pc.inventory, screenWidth, screenHeight, itemFilter=filterPotentialWeapon, onAnswer=self.actionWieldItem, query="Wield what?", interface=self.interface, name = "wield-item")
	def actionTurn(self, direction):
		self.pc.facing = direction
		self.pc.generateFov()
		self.timekeeper.tick()
	def actionDisplace(self):
		self.interface.messages.add( "Displace whom or what?" )
		DirectionPrompt( self.interface, do = self.actionDoDisplace, cancelled = "Cancelled.")
	def actionPickup(self, item):
		def actionPickupAmount(amount):
			if amount <= 0: return
			rv = item.take( amount )
			if self.pc.pickup( rv ): self.actionWait()
		if item.amount > 1:
			AmountChoice( interface=self.interface, query=None, width=60, selection=item.amount, minimum=0, maximum=item.amount, descriptor=ItemCounter(item.itemType), onAnswer = actionPickupAmount, name = "amount-pickup-dialog", priority = 2)
		else:
			rv = item.take( 1 )
			if self.pc.pickup( rv ): self.actionWait()
	def actionDebugRay(self, direction ):
		prj = EffectProjectorWideAngle( self.interface, self.pc.level, HeatEffect(), (self.pc.x, self.pc.y), direction, pi / 2 )
		prj.display()
	def actionDebugBurst(self):
		prj = EffectProjectorRadial( self.interface, self.pc.level, HeatEffect(), (self.pc.x, self.pc.y), 6 )
		prj.display()
	def actionDebugBounce(self ):
		prj = EffectProjectorBouncerBall( self.interface, self.pc.level, HeatEffect(), (self.pc.x, self.pc.y), self.pc.facing, 24 )
		prj.display()
	def actionTryPickup(self):
		tile = self.pc.level.tiles[self.pc.x][self.pc.y]
		if not tile.items:
			self.interface.messages.add( "There's nothing here to pick up." )
		elif len( tile.items ) == 1:
			self.actionPickup( tile.items[0] )
		else:
			descs = []
			vals = []
			for item in tile.items:
				descs.append( item.description() )
				vals.append( item )
			OptionChoice( interface=self.interface, onAnswer=self.actionPickup, width=60, query="What do you want to pick up?", descriptions=descs, values=vals, name="pickup-dialog" )
	def actionDoDisplace(self, direction):
		target = self.pc.getTileRelative( direction )
		if not target.mobile:
			self.interface.messages.add( "There's nothing there to displace." )
			return
		if target.mobile.mayDisplace:
			self.pc.swap( target.mobile )
			self.interface.messages.add( "You displace the %s." % target.mobile.name )
			self.pc.generateFov()
			self.timekeeper.tick()
			self.pc.facing = direction # may be considered a bit of a hack, but
						   # the displaced mob could be considered to
						   # just be dazed and confused after
						   # displacement has taken place
		else:
			self.interface.messages.add( "You can't displace the %s!" % target.mobile.name )
	def actionToggleDoor(self):
		self.interface.messages.add( "Which door?" )
		DirectionPrompt( self.interface, do = self.actionDoToggleDoor, cancelled = "Cancelled.")
	def actionDoToggleDoor(self, direction):
		target = self.pc.getTileRelative( direction )
		if target.type == self.game.gtt.closedDoor:
			target.type = self.game.gtt.openDoor
			self.pc.generateFov()
			self.timekeeper.tick()
			self.interface.messages.add( "You open the door." )
		elif target.type == self.game.gtt.openDoor:
			target.type = self.game.gtt.closedDoor
			self.pc.generateFov()
			self.timekeeper.tick()
			self.interface.messages.add( "You close the door." )
		else:
			self.interface.messages.add( "You see no door there." )
	def actionDebugFovCheat(self):
		self.interface.elements["map"].fovCheat = not self.interface.elements["map"].fovCheat
	def uiLookLocal(self):
		tile = self.pc.level.tiles[self.pc.x][self.pc.y]
		itemlist = [ item.description() for item in tile.items ]
		if not itemlist: return
		firstItem = tile.items[0]
		if firstItem.amount > 1 or firstItem.itemType.unit:
			self.interface.messages.add( "There are %s here." % makeEnglishList( itemlist ) )
		else:
			self.interface.messages.add( "There's %s here." % makeEnglishList( itemlist ) )
	def actionTryMove( self, direction ):
		tick = False
		target = self.pc.getTileRelative( direction )
		if self.pc.mayMove( target ):
			self.pc.moveRelative( direction )
			self.uiLookLocal()
			self.pc.generateFov()
			self.timekeeper.tick()
		elif target.mobile:
			try:
				target.mobile.tryPush( direction )
				if self.pc.mayMove( target ):
					self.pc.moveRelative( direction )
					self.pc.generateFov()
					self.timekeeper.tick()
				else:
					self.interface.messages.add( "There's something in the way." )
			except AttributeError:
				if not target.mobile.friendly:
					self.pc.facing = direction # of course it's a hack that only the player does this..
					self.actionTryMelee( target.mobile )
				else:
					self.interface.messages.add( "The <green>%s</green> is in the way." % target.mobile.name )
		elif target.type == self.game.gtt.closedDoor:
			self.actionDoToggleDoor( direction )
	def actionTryMelee( self, mobile ):
		self.pc.attackMelee( mobile )
		self.timekeeper.tick()
	def actionDesign(self):
		def prototypeReceiver( prototype ):
			self.pc.inventions.insert( 0, prototype )
			#InventoryMenu( prototype.ingredients, screenWidth, screenHeight, onAnswer=nullResponseHandler, query=None, interface=self.interface, name = "examine-item")
			#prototype.create( self.pc )
		DeviceDesigner( self.game, self.pc.knownComponents, self.pc.knownChassises, prototypeReceiver = prototypeReceiver )
	def actionBuild(self):
		def doBuildDevice( prototype ):
			itemsOnGround = self.pc.level.tiles[self.pc.x][self.pc.y].items
			def doBuildDeviceConfirm( really ):
				if not really: return
				for item in prototype.ingredients:
					amountLeft = item.amount
					a = self.pc.inventory.findType( item.itemType )
					if a:
						a = a.take( amountLeft )
						amountLeft -= a.amount
					if amountLeft > 0:
						b = itemsOnGround.findType( item.itemType )
						if b:
							b = b.take( amountLeft )
							amountLeft -= b.amount
					assert amountLeft == 0
				prototype.create( self.pc )
				self.timekeeper.tick()
			if not prototype: return
			text = []
			text.append( prototype.name.singular )
			text.append( "" )
			okay = True
			for item in prototype.ingredients:
				a = self.pc.inventory.findType( item.itemType )
				b = itemsOnGround.findType( item.itemType )
				if a: a = a.amount
				else: a = 0
				if b: b = b.amount
				else: b = 0
				amt = a + b
				if item.amount > amt:
					okay = False
					text.append( "<WARNING>" + item.description() + "</WARNING>" )
				else:
					text.append( item.description() )
			text.append( "" )
			if okay:
				text.append( "Build this device?" )
				confirmDesc = [ "Yes", "No" ]
				confirmVal = [ True, False ]
				OptionChoice( query="<N>".join(text), width=60, descriptions=confirmDesc, values=confirmVal, onAnswer=doBuildDeviceConfirm, interface = self.interface, name = "build-item" ) 
			else:
				text.append( "Can't build device." )
				TextDisplay( text = "<N>".join( text ), width=60, interface = self.interface, name = "cannot-build-device" )
		descs = [ prototype.name.singular for prototype in self.pc.inventions ]
		descs.append( "<DARKBRASS>none</DARKBRASS>" )
		values = self.pc.inventions + [ None ]
		OptionChoice( query=None, width=60, descriptions=descs, values=values, onAnswer=doBuildDevice, interface = self.interface, name = "build-item" ) 
	def actionDebugChickenRay(self, direction ):
		from math import atan2, sin, cos, pi
		# note the TWO times we swap direction of y axis.
		dx, dy = direction
		dy *= -1
		u = atan2( dy, dx )
		du = pi/16.0
		a0 = int(1000*cos(u-du)), -int(1000*sin(u-du))
		a1 = int(1000*cos(u+du)), -int(1000*sin(u+du))
		wi = WheelIndex( direction )
		lof = Fov( self.pc.vision )
		lof.initializeSector( self.pc.x, self.pc.y, a0, a1 )
		lof.spread()
		for x in range(levelWidth):
			for y in range(levelHeight):
				if lof.isLit(x,y):
					#try: createChickenAt( x, y )
					#except CannotGenerate: pass
					mobile = self.pc.level.tiles[x][y].mobile
					if mobile and mobile != self.pc:
						mobile.die()
	def actionDebugChickenLine( self, direction ):
		dx, dy = self.pc.facing
		x, y = self.pc.x, self.pc.y
		try:
			while True:
				x += dx
				y += dy
				createChickenAt( x, y )
		except CannotGenerate: pass
	def actionDebugFoxJump( self ):
		for i in range(4):
			self.interface.messages.add( "The quick brown fox jumps over the lazy dog.")
			self.interface.messages.add("<green>The quick brown fox jumps over the lazy dog.</green>")
			self.interface.messages.add("<red>The quick brown fox jumps over the lazy dog.</red>")
